//
//  DecoPlanner4.m
//  DiveCalcTestHarness
//
//  Created by Donald MacKay on 11-04-25.
//  Copyright 2011 H2O Worx. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <math.h>
#import "DecoPlanner4.h"
#import "Constants.h"
#import "Utilities.h"

@implementation DivePhaseParameters

@synthesize startDepth, endDepth, ambientATA, ambientATAMinusWVP, waterVaporPressure, descentRate, time;
-(id) initWithStartDepth:(double) sDepth endDepth:(double) eDepth
              ambientATA:(double)  aATA waterVaporPressure:(double) waterVP
             descentRate: (double) dRate Time:(double) t
{
    self = [super init];
    if (self) {
        startDepth = [NSNumber numberWithDouble: sDepth];
        endDepth = [NSNumber numberWithDouble: eDepth];
        ambientATA = [NSNumber numberWithDouble: aATA];
        waterVaporPressure = [NSNumber numberWithDouble: waterVP];
        descentRate = [NSNumber numberWithDouble: dRate];
        time = [NSNumber numberWithDouble: t];
        return self;
    } else {
        
        return nil;
    }
}

-(id) init 
{
    self = [super init];
    if (self) {
        startDepth = [NSNumber numberWithDouble:0.0];
        endDepth = [NSNumber numberWithDouble:0.0];
        ambientATA = [NSNumber numberWithDouble:0.0];
        waterVaporPressure = [NSNumber numberWithDouble: 0.0];
        descentRate = [NSNumber numberWithDouble:0.0];
        time = [NSNumber numberWithDouble:0.0];
    }
    return self;
}

-(NSString *) description {
    return [NSString stringWithFormat: @"Start depth: %1.4, End Depth: %1.4f, \
            Ambient ATA: %1.4f, Water Vapor Pressure: %1.4f,Descent Rate: %1.4f, Time: %1.4f",
            [startDepth doubleValue],
            [endDepth doubleValue],
            [ambientATA doubleValue],
            [waterVaporPressure doubleValue],
            [descentRate doubleValue],
            [time doubleValue]];
}


    
- (void)dealloc
{
    [super dealloc];
}

@end


@implementation Tissue

@synthesize n2TissuePressure, heTissuePressure, totalPressure;

-(id) initTissueWithPPN2: (double) ppN2 andPPHe: (double) ppHe
{
    self = [self init];
    if (self) {
        n2TissuePressure = ppN2;
        heTissuePressure = ppHe;
        totalPressure = n2TissuePressure + heTissuePressure;
    }
    return self;
}

-(id) init 
{
    self = [super init];
    if (self) {
        n2TissuePressure =  0.0;
        heTissuePressure =  0.0;
        totalPressure = 0.0;
    }
    return self;
}

-(void) makeTotal{
    totalPressure = n2TissuePressure + heTissuePressure;
}

- (void)dealloc
{
    [super dealloc];
}

-(NSString *) description {
    return [NSString stringWithFormat: @"Helium: %1.4f Nitrogen: %1.4f Total Pressure: %1.4f",
            heTissuePressure,
            n2TissuePressure,
            totalPressure];
}

@end

/**
 *  DecoPlanner4 calculates a complete dive profile based on user input.
 *
 *  Created: 2011/04/25 Dan MacKay (DBM)
 *
 *  Revision History:
 *      2011/04/25 DBM - Added initializing compartmentArray,
 *          as well as the N2 and He arrays to init()
 *      2011/04/27 DBM - Added calcDescent
 */
@implementation DecoPlanner4

@synthesize diveProfile, compartmentArray;

- (id)init
{
    self = [super init];
    if (self) {
        
        /***
         * Set up and init the tissue compartment array
         *      There is a little work to add to this:
         *          1. For repetitive dives ppN2 and ppHe should be set to their current levels
         *          2. For altitude diving once again both values should be adjusted to reflect
         *             a lower starting atmosheric pressure.
         */
        compartmentArray = [[NSMutableArray alloc] initWithCapacity:kNumberOfCompartments];
        for (int i = 0; i < kNumberOfCompartments; i++) {
            Tissue *theCompartment = [[Tissue alloc] initTissueWithPPN2:0.0
                                                                andPPHe:0.0];
            [compartmentArray addObject:theCompartment];
            [theCompartment release];
        }
        /***
         * Initialize the Dive Profile array
         */
        diveProfile = [[NSMutableDictionary alloc] init];
        
        /***
         * Build the N2 and He Halflife arrays and add them to
         * the diveProfile
         */
        NSArray *N2HalfLives = [NSArray arrayWithObjects:[NSNumber numberWithDouble:kHalfLifeN2Cmp0],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp1],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp2],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp3],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp4],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp5],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp6],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp7],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp8],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp9],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp10],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp11],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp12],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp13],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp14],
                            [NSNumber numberWithDouble:kHalfLifeN2Cmp15],
                                nil];
        [diveProfile setObject:N2HalfLives forKey:kN2HalfLifeDictKey];

        /***
         * Build the He and He Halflife arrays and add them to
         * the diveProfile
         */
        NSArray *HeHalfLives = [NSArray arrayWithObjects:[NSNumber numberWithDouble:kHalfLifeHeCmp0],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp1],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp2],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp3],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp4],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp5],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp6],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp7],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp8],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp9],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp10],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp11],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp12],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp13],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp14],
                                [NSNumber numberWithDouble:kHalfLifeHeCmp15],
                                nil];
        [diveProfile setObject:HeHalfLives forKey:kHeHalfLifeDictKey];    
    }

    NSArray *N2ACoefficients = [NSArray arrayWithObjects:[NSNumber numberWithDouble:kZHL_16B_N2aCmp0],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp1],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp2],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp3],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp4],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp5],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp6],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp7],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp8],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp9],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp10],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp11],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp12],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp13],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp14],
                                [NSNumber numberWithDouble:kZHL_16B_N2aCmp15],
                                nil];
    [diveProfile setObject:N2ACoefficients forKey:kZHL_l6B_N2_a_DictKey];
    
    NSArray *N2BCoefficients = [NSArray arrayWithObjects:[NSNumber numberWithDouble:kZHL_16B_N2bCmp0],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp1],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp2],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp3],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp4],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp5],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp6],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp7],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp8],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp9],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp10],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp11],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp12],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp13],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp14],
                                [NSNumber numberWithDouble:kZHL_16B_N2bCmp15],
                                nil];
    [diveProfile setObject:N2BCoefficients forKey:kZHL_l6B_N2_b_DictKey];
    
    
    NSArray *HeACoefficients = [NSArray arrayWithObjects:[NSNumber numberWithDouble:kZHL_16B_HeaCmp0],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp1],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp2],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp3],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp4],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp5],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp6],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp7],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp8],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp9],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp10],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp11],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp12],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp13],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp14],
                                [NSNumber numberWithDouble:kZHL_16B_HeaCmp15],
                                nil];
    [diveProfile setObject:HeACoefficients forKey:kZHL_l6B_He_a_DictKey];
    
    NSArray *HeBCoefficients = [NSArray arrayWithObjects:[NSNumber numberWithDouble:kZHL_16B_HebCmp0],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp1],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp2],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp3],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp4],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp5],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp6],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp7],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp8],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp9],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp10],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp11],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp12],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp13],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp14],
                                [NSNumber numberWithDouble:kZHL_16B_HebCmp15],
                                nil];
    [diveProfile setObject:HeBCoefficients forKey: kZHL_16B_He_b_DictKey];

    return self;
}

/***
 *  calcDescent calculates tissue gas loading during the descent phase.
 *
 *  This method determines the gas loading of N2 and He (if present) for all 16
 *  compartments based on a descent rate and gas mix provided by the user. Can be used for
 *  multilevel dives as well as long as the correct start and end depths, time, and
 *  current gas loadings are provided.
 *
 *  Created: 2011/04/27 DBM
 */
-(void) calcDescentWithDiveParameters: (DivePhaseParameters *) diveParams andGasMix:(GasMix *) mix {
    double ambMinusWVp = [diveParams.ambientATA floatValue] -  [diveParams.waterVaporPressure floatValue]; /* initial ambient ATA in MSW minus water vapor */          
    double time = ([diveParams.endDepth floatValue] - [diveParams.startDepth floatValue]) / [diveParams.descentRate floatValue];
    double startPPN2 = 0.79;
    double startPPHe = 0.0;
    
    
    /* initial inspired (alveolar) inert gas pressure in breathing mix */
    double initialInspiredGasPressHe = ambMinusWVp * mix.heliumContent;
    /* initial compartment inert gas pressure */
    double initialCompartmentPressHe = ambMinusWVp * startPPHe;
    /* rate of change in inspired gas pressure with change in ambient pressure */
    double rateIspiredVsAbmientHe = [diveParams.descentRate floatValue] * mix.heliumContent;
    
    /* initial inspired (alveolar) inert gas pressure in breathing mix */
    double initialInspiredGasPressN2 = ambMinusWVp * mix.nitrogenContent;
    /* initial compartment inert gas pressure */
    double initialCompartmentPressN2 = ambMinusWVp * startPPN2;
    /* rate of change in inspired gas pressure with change in ambient pressure */
    double rateIspiredVsAbmientN2 = [diveParams.descentRate floatValue] * mix.nitrogenContent;

    /***
     * Iterate through the compartment array filling in the times as you go
     */

    double halfTimeConstHe = 0.0;                                           
    double halfTimeConstN2 = 0.0;
    Tissue *tmpCompartment = nil;
    
    for (int i = 0; i <= 15; i++) {
        /* Set up the half time constant for this time through */
        halfTimeConstHe = log(2.0)/[[[diveProfile objectForKey:kHeHalfLifeDictKey] objectAtIndex:i] floatValue];                                       
        halfTimeConstN2 = log(2.0)/[[[diveProfile objectForKey:kN2HalfLifeDictKey] objectAtIndex:i] floatValue];
#ifdef DEBUG
        NSLog(@"helium half-life: %1.4f",[[[diveProfile objectForKey:kHeHalfLifeDictKey] objectAtIndex:i] floatValue] );
        NSLog(@"nitrogen half-life: %1.4f",[[[diveProfile objectForKey:kN2HalfLifeDictKey] objectAtIndex:i] floatValue] );
#endif

        tmpCompartment = [compartmentArray objectAtIndex:i];


        /* Fill in the He and N2 components for each compartment */
        tmpCompartment.heTissuePressure = initialInspiredGasPressHe + rateIspiredVsAbmientHe * (time - 1/halfTimeConstHe) - (initialInspiredGasPressHe - initialCompartmentPressHe - (rateIspiredVsAbmientHe/halfTimeConstHe))*exp(-halfTimeConstHe * time);


        tmpCompartment.n2TissuePressure = initialInspiredGasPressN2 + rateIspiredVsAbmientN2 * (time - 1/halfTimeConstN2) - (initialInspiredGasPressN2 - initialCompartmentPressN2 - (rateIspiredVsAbmientN2/halfTimeConstN2))*exp(-halfTimeConstN2 * time);

        [tmpCompartment makeTotal];
        
        tmpCompartment = nil;
    }
    
}

-(void) calcBottomTimeWithDiveParameters: (DivePhaseParameters *) diveParams andGasMix:(GasMix *) mix {
  
    double halfTimeConstHe = 0.0;                                           
    double halfTimeConstN2 = 0.0;
    double inspiredHeGradient = ([[diveParams endDepth] doubleValue] + [[diveParams ambientATA] doubleValue]) * mix.heliumContent;    
    double inspiredN2Gradient = ([[diveParams endDepth] doubleValue] + [[diveParams ambientATA] doubleValue]) * mix.nitrogenContent;
    
    Tissue *tmpCompartment = nil;

    /***
     * Iterate through the compartment array filling in the times as you go
     */ 
    for (int i = 0; i < kNumberOfCompartments; i++) {
        /* Set up the half time constant for this time through */
        halfTimeConstHe = log(2.0)/[[[diveProfile objectForKey:kHeHalfLifeDictKey] objectAtIndex:i] floatValue];                                       
        halfTimeConstN2 = log(2.0)/[[[diveProfile objectForKey:kN2HalfLifeDictKey] objectAtIndex:i] floatValue];
#ifdef DEBUG
        NSLog(@"helium half-life: %1.4f",[[[diveProfile objectForKey:kHeHalfLifeDictKey] objectAtIndex:i] floatValue] );
        NSLog(@"nitrogen half-life: %1.4f",[[[diveProfile objectForKey:kN2HalfLifeDictKey] objectAtIndex:i] floatValue] );
#endif
        
        tmpCompartment = [compartmentArray objectAtIndex:i];
        
        
        /* Fill in the He and N2 components for each compartment */
        tmpCompartment.heTissuePressure = tmpCompartment.heTissuePressure + (inspiredHeGradient - tmpCompartment.heTissuePressure) * (1 - exp(-halfTimeConstHe * [[diveParams time] doubleValue]));
        
        
        tmpCompartment.n2TissuePressure = tmpCompartment.n2TissuePressure + (inspiredN2Gradient - tmpCompartment.n2TissuePressure) * (1 - exp(-halfTimeConstN2 * [[diveParams time] doubleValue]));
        
        [tmpCompartment makeTotal];
        
        tmpCompartment = nil;
    }
    
}
/***
 * This method will iterate through the current gas loading of the compartments and return the depth of the
 *  leading compartment
 */
-(double) findLeadingCompartment: (double) adjustATAValue {

    Tissue *tmpCompartment = nil;
    double tmpLeadingCompartmentDepth, curLeadingCompartmentDepth = 0.0;
    double N2Acoef, N2Bcoef, HeAcoef, HeBcoef  = 0.0;
    double avgAcoef, avgBcoef  = 0.0;
    double N2CmptGasLoad, HeCmptGasLoad, totalGasLoad  = 0.0;

    for (int i = 0; i < kNumberOfCompartments; i++) {
        /***
         * First we have to get the gas loadings in order to average the the co-efficients
         */
        tmpCompartment = [compartmentArray objectAtIndex:i];
        N2CmptGasLoad = tmpCompartment.n2TissuePressure;
        HeCmptGasLoad = tmpCompartment.heTissuePressure;
        totalGasLoad = tmpCompartment.totalPressure;
        
        N2Acoef = [[[diveProfile objectForKey:kZHL_l6B_N2_a_DictKey] objectAtIndex:i] floatValue];                                       
        N2Bcoef = [[[diveProfile objectForKey:kZHL_l6B_N2_b_DictKey] objectAtIndex:i] floatValue];                                       
        HeAcoef = [[[diveProfile objectForKey:kZHL_l6B_He_a_DictKey] objectAtIndex:i] floatValue];                                       
        HeBcoef = [[[diveProfile objectForKey:kZHL_16B_He_b_DictKey] objectAtIndex:i] floatValue];                                       
        
        avgAcoef = (((N2Acoef * N2CmptGasLoad) + (HeAcoef * HeCmptGasLoad)) / totalGasLoad) * adjustATAValue;
        avgBcoef = (((N2Bcoef * N2CmptGasLoad) + (HeBcoef * HeCmptGasLoad)) / totalGasLoad);
#ifdef DEBUG
        NSLog(@"The average A coeffiecent is: %1.6f", avgAcoef);
        NSLog(@"The average B coeffiecent is: %1.6f", avgBcoef);
#endif        
        tmpLeadingCompartmentDepth = ((totalGasLoad - avgAcoef) * avgBcoef) - adjustATAValue;
        
#ifdef DEBUG
        NSLog(@"The Tmp leading compartment pressure is: %1.6f", tmpLeadingCompartmentDepth);
#endif
        
        curLeadingCompartmentDepth = (tmpLeadingCompartmentDepth > curLeadingCompartmentDepth) ? tmpLeadingCompartmentDepth : curLeadingCompartmentDepth;
#ifdef DEBUG
        NSLog(@"The current leading compartment pressure is: %1.6f", curLeadingCompartmentDepth);
#endif
    }
    return curLeadingCompartmentDepth;
}

- (void)dealloc
{
    [compartmentArray release];
    [super dealloc];
}

@end
